#include "StdAfx.h"
#include "ClientEngine.h"
#include "UtAssert.h"
#include "ueh.h"

CAtlArray<CClientEngine::TestGroupItem>* CClientEngine::s_pTestGroups;

CPUTCRT_API void LaunchUtSequence()
{
	UEH_Initialization();
	CoInitialize(NULL);

	{
		CClientEngine csClientEngine;
		csClientEngine.Run2();
		CClientEngine::Free();
	}

	CoUninitialize();
	UEH_Termination();
}

CPUTCRT_API int RegisterUtGroup(LPMakeInstanceFunc fMakeInstance, LPDestroyInstanceFunc fDestroyInstance, LPGetTestsFunc fGetTests, LPCWSTR lpName)
{
	return CClientEngine::Add(fMakeInstance, fDestroyInstance, fGetTests, lpName);
}

CAtlArray<CClientEngine::TestGroupItem> & CClientEngine::GetRegisterTestGroups()
{
	if (s_pTestGroups == NULL)
	{
		s_pTestGroups = new CAtlArray<CClientEngine::TestGroupItem>();
	}

	return *s_pTestGroups;
}

void CClientEngine::Free()
{
	delete s_pTestGroups;
	s_pTestGroups = NULL;
}

CClientEngine::CClientEngine(void) :
	m_eFilter(efNone)
{
	//subscribe report engines
	m_csReportBuilder.Subscribe(&m_csTestRunnerExchange2);
	m_csReportBuilder.Subscribe(&m_csTextReport);
	m_csReportBuilder.Subscribe(&m_csXmlReport);
}

CClientEngine::~CClientEngine(void)
{
}

int CClientEngine::Add(LPMakeInstanceFunc fMakeInstance, LPDestroyInstanceFunc fDestroyInstance, LPGetTestsFunc fGetTests, LPCWSTR lpName) 
{ 
	TestGroupItem st;
	st.fMakeInstance = fMakeInstance;
	st.fDestroyInstance = fDestroyInstance;
	st.fGetTests = fGetTests;
	st.lpName = lpName;
	
	return (int)GetRegisterTestGroups().Add(st); 
}

//void CClientEngine::Run()
//{
//	m_aTestGroups.Copy(GetRegisterTestGroups());
//
//	for (size_t t = 0;t < m_aTestGroups.GetCount(); ++t)
//	{
//		const TestGroupItem & rTestGroupItem = m_aTestGroups.GetAt(t);
//
//		CAtlStringW wstrReport;
//		void * pTestGroup;
//		ERunStatus eRunStatus;
//		BOOL bRet = CreateTestGroupSehWrapper(rTestGroupItem.fMakeInstance, pTestGroup, eRunStatus, wstrReport);
//		if (bRet)
//		{
//			const UtTestEntry * pTest = (rTestGroupItem.fGetTests)();
//			for (;pTest->lpFunc; ++pTest)
//			{
//				RunTestSehWrapper(pTestGroup, pTest->lpFunc, eRunStatus, wstrReport);
//			}
//
//			DeleteTestGroupSehWrapper(rTestGroupItem.fDestroyInstance, pTestGroup, eRunStatus, wstrReport);
//		}
//	}
//}

ERunStatus CClientEngine::MakeRunStatus(const CUtException & rEx)
{
	switch(rEx.m_eActionCode)
	{
	case CUtException::acAbort:
	case CUtException::acBreak:
		return Stopped;
	}

	switch(rEx.m_eReportStatus)
	{
	case CUtException::rsInconclusive:
		return Inconclusive;
	case CUtException::rsWarning:
		return Warning;
	}

	return Failed;
}

BOOL CClientEngine::CreateTestGroupSehWrapper(LPMakeInstanceFunc func, void* & pTestGroup, ERunStatus & rRunStatus, CAtlStringW & wstrReport, CUtException::EActionCode & rActionCode)
{
	ATLASSERT(func != NULL);
	BOOL bRet = TRUE;
	wstrReport.Empty();
	rRunStatus = Running;
	rActionCode = CUtException::acContinue;
	//since project could be build with any /EH option both SEH and C++ have to be handled
	LPEXCEPTION_POINTERS lpEI = NULL;
	__try
	{
		bRet = CreateTestGroupCppWrapper(func, pTestGroup, rRunStatus, wstrReport, rActionCode);
	}
	__except(((NULL != (lpEI = GetExceptionInformation())) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH))
	{
		bRet = FALSE;
		rRunStatus = Failed;

		if (sizeof(lpEI->ExceptionRecord->ExceptionAddress) == 8)
		{
			wstrReport.Format(L"SEH Exception code 0x%08X at address 0x%16I64X during creation a test group", 
				lpEI->ExceptionRecord->ExceptionCode, lpEI->ExceptionRecord->ExceptionAddress);
		} else
		{
			wstrReport.Format(L"SEH Exception code 0x%08X at address 0x%08X during creation a test group", 
				lpEI->ExceptionRecord->ExceptionCode, lpEI->ExceptionRecord->ExceptionAddress);
		}
	}
	return bRet;
}

BOOL CClientEngine::CreateTestGroupCppWrapper(LPMakeInstanceFunc func, void* & pTestGroup, ERunStatus & rRunStatus, CAtlStringW & wstrReport, CUtException::EActionCode & rActionCode)
{
	ATLASSERT(func != NULL);
	BOOL bRet = TRUE;
	try
	{
		pTestGroup = (func)();
		if (pTestGroup == NULL)
		{
			rRunStatus = Failed;
			wstrReport = L"Test group was not created";
			bRet = FALSE;
		}
	}
	catch(CUtException & ex)
	{
		bRet = FALSE;
		rRunStatus = MakeRunStatus(ex);

		if (ex.m_lpMessage != NULL)
		{
			wstrReport.Format(L"Assert with message\"%s\" File %S (%i) Function %S during creation a test group", ex.m_lpMessage, ex.m_lpFile, ex.m_nLine, ex.m_lpFunction);
		} else
		{
			wstrReport.Format(L"Assert File %S (%i) Function %S during creation a test group", ex.m_lpFile, ex.m_nLine, ex.m_lpFunction);
		}
	}
	catch(...)
	{
		bRet = FALSE;
		rRunStatus = Failed;
		wstrReport.Format( L"Exception during creation a test group, last error code 0x%08X", GetLastError());
	}
	return bRet;
}

void CClientEngine::DeleteTestGroupSehWrapper(LPDestroyInstanceFunc func, void* pTestGroup, ERunStatus & rRunStatus, CAtlStringW & wstrReport, CUtException::EActionCode & rActionCode)
{
	rRunStatus = Succeeded;
	wstrReport.Empty();
	rActionCode = CUtException::acContinue;
	//since project could be build with any /EH option both SEH and C++ have to be handled
	LPEXCEPTION_POINTERS lpEI = NULL;
	__try
	{
		DeleteTestGroupCppWrapper(func, pTestGroup, rRunStatus, wstrReport, rActionCode);
	}
	__except(((NULL != (lpEI = GetExceptionInformation())) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH))
	{
		rRunStatus = Failed;
		if (sizeof(lpEI->ExceptionRecord->ExceptionAddress) == 8)
		{
			wstrReport.Format(L"SEH Exception code 0x%08X at address 0x%16I64X during destroying a test group", 
				lpEI->ExceptionRecord->ExceptionCode, lpEI->ExceptionRecord->ExceptionAddress);
		} else
		{
			wstrReport.Format(L"SEH Exception code 0x%08X at address 0x%08X during destroying a test group", 
				lpEI->ExceptionRecord->ExceptionCode, lpEI->ExceptionRecord->ExceptionAddress);
		}
	}
}

void CClientEngine::DeleteTestGroupCppWrapper(LPDestroyInstanceFunc func, void* pTestGroup, ERunStatus & rRunStatus, CAtlStringW & wstrReport, CUtException::EActionCode & rActionCode)
{
	try
	{
		(func)(pTestGroup);
	}
	catch(CUtException & ex)
	{
		rRunStatus = MakeRunStatus(ex);
		if (ex.m_lpMessage != NULL)
		{
			wstrReport.Format(L"Assert with message\"%s\" File %S (%i) Function %S during destroying a test group", ex.m_lpMessage, ex.m_lpFile, ex.m_nLine, ex.m_lpFunction);
		} else
		{
			wstrReport.Format(L"Assert File %S (%i) Function %S during destroying a test group", ex.m_lpFile, ex.m_nLine, ex.m_lpFunction);
		}
	}
	catch(...)
	{
		rRunStatus = Failed;
		wstrReport.Format( L"Exception during destroying a test group, last error code 0x%08X", GetLastError());
	}
}

void CClientEngine::RunTestSehWrapper(void* pTestGroup, LPTestFunc func, ERunStatus & rRunStatus, CAtlStringW & wstrReport, CUtException::EActionCode & rActionCode)
{
	ATLASSERT((pTestGroup != NULL) && (func != NULL));
	rRunStatus = Running;
	wstrReport.Empty();
	rActionCode = CUtException::acContinue;
	
	//since project could be build with any /EH option both SEH and C++ have to be handled
	LPEXCEPTION_POINTERS lpEI = NULL;
	__try
	{
		RunTestCppWrapper(pTestGroup, func, rRunStatus, wstrReport, rActionCode);
	}
	__except(((NULL != (lpEI = GetExceptionInformation())) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH))
	{
		rRunStatus = Failed;
		if (sizeof(lpEI->ExceptionRecord->ExceptionAddress) == 8)
		{
			wstrReport.Format(L"SEH Exception code 0x%08X at address 0x%16I64X", 
				lpEI->ExceptionRecord->ExceptionCode, lpEI->ExceptionRecord->ExceptionAddress);
		} else
		{
			wstrReport.Format(L"SEH Exception code 0x%08X at address 0x%08X", 
				lpEI->ExceptionRecord->ExceptionCode, lpEI->ExceptionRecord->ExceptionAddress);
		}
	}
}

void CClientEngine::RunTestCppWrapper(void* pTestGroup, LPTestFunc func, ERunStatus & rRunStatus, CAtlStringW & wstrReport, CUtException::EActionCode & rActionCode)
{
	ATLASSERT((pTestGroup != NULL) && (func != NULL));
	try
	{
		CUtTestBase * pTestGroupInstance = (CUtTestBase *)pTestGroup;
		(pTestGroupInstance->*func)();
		rRunStatus = Succeeded;
	}
	catch(CUtException & ex)
	{
		rRunStatus = MakeRunStatus(ex);

		if (ex.m_lpMessage != NULL)
		{
			wstrReport.Format(L"Assert with message\"%s\" File %S (%i) Function %S", ex.m_lpMessage, ex.m_lpFile, ex.m_nLine, ex.m_lpFunction);
		} else
		{
			wstrReport.Format(L"Assert File %S (%i) Function %S", ex.m_lpFile, ex.m_nLine, ex.m_lpFunction);
		}
	}
	catch(...)
	{
		rRunStatus = Failed;
		wstrReport.Format(L"Exception, last error code 0x%08X", GetLastError());
	}
}


void CClientEngine::Run2()
{
	CAtlStringW wstrReport;
	ERunStatus eRunStatus;
	CUtException::EActionCode eActionCode;

	CRBMultiMap<DWORD, SortedRunGroupItem> mapSortedRunGroups;
	if (PrepareTestSet(mapSortedRunGroups)) 
	{
		m_csReportBuilder.Start(GetFilter());
		POSITION posGroup = mapSortedRunGroups.GetHeadPosition();
		while(posGroup)
		{
			const CRBMultiMap<DWORD, SortedRunGroupItem>::CPair *pGroupPair = mapSortedRunGroups.GetNext(posGroup);
			const SortedRunGroupItem & rSortedRunGroupItem = pGroupPair->m_value;
			void* pTestGroup = NULL;
			BOOL bGroupRet = CreateTestGroupSehWrapper(rSortedRunGroupItem.fMakeInstance, pTestGroup, eRunStatus, wstrReport, eActionCode);
			m_csReportBuilder.GroupStart(rSortedRunGroupItem.lpName, pGroupPair->m_key);
			CTime sGroupStart = CTime::GetCurrentTime();
			if ((bGroupRet) && (eActionCode == CUtException::acContinue))
			{
				POSITION posTest = rSortedRunGroupItem.mapUtTestEntries.GetHeadPosition();
				while(posTest)
				{
					const CRBMultiMap<DWORD, const UtTestEntry*>::CPair *pTestPair = rSortedRunGroupItem.mapUtTestEntries.GetNext(posTest);
					m_csReportBuilder.TestStart(rSortedRunGroupItem.lpName, pTestPair->m_value->lpName, pGroupPair->m_key, pTestPair->m_key);
					CTime sTestStart = CTime::GetCurrentTime();

					RunTestSehWrapper(pTestGroup, pTestPair->m_value->lpFunc, eRunStatus, wstrReport, eActionCode);

					CTimeSpan sTestTime = CTime::GetCurrentTime() - sTestStart;
					m_csReportBuilder.TestEnd(rSortedRunGroupItem.lpName, pTestPair->m_value->lpName, eRunStatus, wstrReport, sTestTime.GetTimeSpan(), pGroupPair->m_key, pTestPair->m_key);

					if (eActionCode != CUtException::acContinue)
						break;
				}

				if (eActionCode != CUtException::acAbort)
					DeleteTestGroupSehWrapper(rSortedRunGroupItem.fDestroyInstance, pTestGroup, eRunStatus, wstrReport, eActionCode);
			}
			CTimeSpan sGroupTime = CTime::GetCurrentTime() - sGroupStart;
			m_csReportBuilder.GroupEnd(rSortedRunGroupItem.lpName, eRunStatus, wstrReport, sGroupTime.GetTimeSpan(), pGroupPair->m_key);

			if (eActionCode == CUtException::acAbort)
				break;

		} //while(posGroup)
		m_csReportBuilder.End();
	} //if (PrepareTestSet(csSortedRunGroups)) 
}

BOOL CClientEngine::PrepareTestSet(CRBMultiMap<DWORD, SortedRunGroupItem> & rSortedRunGroups)
{
	m_aTestGroups.Copy(GetRegisterTestGroups());

	TestPackTransData csTestPackTransData;
	//fill csTestPackTransData and set all tests and groups to run
	for (size_t t = 0; t < m_aTestGroups.GetCount(); ++t)
	{
		TestGroupTransData & rTestGroupTransData = csTestPackTransData.aTestGroups.GetAt(csTestPackTransData.aTestGroups.Add());
		const TestGroupItem & rGroup = m_aTestGroups.GetAt(t);
		rTestGroupTransData.wstrName = rGroup.lpName;

		const UtTestEntry * pTest = (rGroup.fGetTests)();
		for (;pTest->lpFunc; ++pTest)
		{
			TestRunTransData & rTestRunTransData = rTestGroupTransData.aTestRuns.GetAt(rTestGroupTransData.aTestRuns.Add());
			rTestRunTransData.wstrName = pTest->lpName;
		}
	}

	BOOL bRet = TRUE;
	if (IsServerConnected())
	{ //server connection is the first priority
		m_eFilter = efInteractive;
		bRet = m_csTestRunnerExchange2.DefineTestsToRun(csTestPackTransData);
	} else if (IsXmlFilterLoaded())
	{ //xml file filter
		m_eFilter = efFile;
		bRet = m_csXmlFileFilter.DefineTestsToRun(csTestPackTransData);
	}
	
	if (bRet)
		bRet = SortTestGroupByLParam(csTestPackTransData, rSortedRunGroups);

	return bRet;
}

CClientEngine::SortedRunGroupItem::SortedRunGroupItem(const CClientEngine::SortedRunGroupItem & r)
{
	fMakeInstance = r.fMakeInstance;
	fDestroyInstance = r.fDestroyInstance;
	lpName = r.lpName;

	POSITION pos = r.mapUtTestEntries.GetHeadPosition();
	while(pos)
	{
		const CRBMultiMap<DWORD, const UtTestEntry*>::CPair * p = r.mapUtTestEntries.GetNext(pos);
		mapUtTestEntries.Insert(static_cast<long>(p->m_key), p->m_value);
	}
};

BOOL CClientEngine::SortTestGroupByLParam(const TestPackTransData & rTestPackTransData, CRBMultiMap<DWORD, SortedRunGroupItem> & mapSortedRunGroups)
{
	//mapSortedRunGroups.RemoveAll();
	if (m_aTestGroups.GetCount() != rTestPackTransData.aTestGroups.GetCount())
		return FALSE;

	for (size_t t = 0; t < m_aTestGroups.GetCount(); ++t)
	{
		const TestGroupItem & rTestGroupItem = m_aTestGroups.GetAt(t);
		const TestGroupTransData & rTestGroupTransData = rTestPackTransData.aTestGroups.GetAt(t);
		
		if (rTestGroupTransData.wstrName != rTestGroupItem.lpName)
			return FALSE;

		if (rTestGroupTransData.bRun == FALSE)
			continue;

		SortedRunGroupItem sSortedRunGroupItem;
		sSortedRunGroupItem.fMakeInstance = rTestGroupItem.fMakeInstance;
		sSortedRunGroupItem.fDestroyInstance = rTestGroupItem.fDestroyInstance;
		sSortedRunGroupItem.lpName = rTestGroupItem.lpName;

		const UtTestEntry * pTest = (rTestGroupItem.fGetTests)();
		for (size_t t = 0;(pTest->lpFunc) && (t < rTestGroupTransData.aTestRuns.GetCount()); ++pTest, ++t)
		{
			const TestRunTransData & rTestRunTransData = rTestGroupTransData.aTestRuns.GetAt(t);
			if (rTestRunTransData.wstrName != pTest->lpName)
				return FALSE;
			if (rTestRunTransData.bRun)
				sSortedRunGroupItem.mapUtTestEntries.Insert(static_cast<long>(rTestRunTransData.lParam), pTest);
		}
		
		mapSortedRunGroups.Insert(static_cast<long>(rTestGroupTransData.lParam), sSortedRunGroupItem);
	}

	return TRUE;
}